Replace the GUI Builder with a Maven-first Codename One app - #5523
Replace the GUI Builder with a Maven-first Codename One app#5523shai-almog wants to merge 105 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR replaces the legacy Swing “Settings-era” GUI Builder with a Maven-first, standalone Codename One desktop app under scripts/guibuilder, integrated with the Maven plugin via mvn cn1:guibuilder. It also adds the minimal core framework changes required to support the new editor (protected regions in the code editor, corrected baseline alignment in LayeredLayout, and null-safety fixes).
Changes:
- Introduces the new standalone GUI Builder app (common + JavaSE modules), demo project assets, and interaction/unit tests.
- Extends core editor/layout APIs to support protected generated regions and correct baseline alignment behavior.
- Updates Maven plugin + release/CI workflows to build, test, and publish the new GUI Builder artifact (
com.codenameone:codenameone-guibuilder).
Reviewed changes
Copilot reviewed 44 out of 45 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/guibuilder/tools/guibuilder-mcp-client.mjs | Adds a local Node MCP client for driving/inspecting the GUI Builder over MCP. |
| scripts/guibuilder/pom.xml | Adds standalone GUI Builder Maven reactor parent (Java 17) with publishing profile. |
| scripts/guibuilder/javase/src/test/java/com/codename1/guibuilder/GeneratedSourceTest.java | Verifies generated sources compile together (form + model strategies + guided constraints). |
| scripts/guibuilder/javase/src/test/java/com/codename1/guibuilder/CodeEditorInteractionTest.java | Tests protected region behavior and caret positioning in the pure editor. |
| scripts/guibuilder/javase/src/desktop/java/com/codename1/guibuilder/CodenameOneGUIBuilderStub.java | Desktop stub/launcher wiring + self-tests for editor/guided layout/interaction. |
| scripts/guibuilder/javase/src/desktop/java/com/codename1/guibuilder/CodenameOneGUIBuilderLauncher.java | Small main-class wrapper for the executable jar. |
| scripts/guibuilder/javase/pom.xml | Defines the published com.codenameone:codenameone-guibuilder JavaSE module and executable-jar profile. |
| scripts/guibuilder/demo-project/src/main/guibuilder/com/example/TableLayoutForm.gui | Demo GUI fixture for TableLayout behaviors. |
| scripts/guibuilder/demo-project/src/main/guibuilder/com/example/NestedLayoutsForm.gui | Demo GUI fixture for nested layout hierarchy behaviors. |
| scripts/guibuilder/demo-project/src/main/guibuilder/com/example/LoginForm.gui | Demo GUI fixture for a basic form. |
| scripts/guibuilder/demo-project/src/main/guibuilder/com/example/GuidedLayoutForm.gui | Demo GUI fixture for Guided Layout constraints and baseline snapping. |
| scripts/guibuilder/demo-project/src/main/guibuilder/com/example/GridLayoutForm.gui | Demo GUI fixture for GridLayout reorder/cell behaviors. |
| scripts/guibuilder/demo-project/src/main/guibuilder/com/example/BoxXLayoutForm.gui | Demo GUI fixture for horizontal BoxLayout scrolling/reorder. |
| scripts/guibuilder/demo-project/src/main/guibuilder/com/example/BorderDropForm.gui | Demo GUI fixture for BorderLayout drop/constraint behaviors. |
| scripts/guibuilder/demo-project/src/main/css/theme.css | Demo project theme for previewing styling + dark mode. |
| scripts/guibuilder/common/src/test/java/com/codename1/guibuilder/project/ProjectBindingTest.java | Unit test for parsing the modern binding format. |
| scripts/guibuilder/common/src/test/java/com/codename1/guibuilder/model/GuiDocumentTest.java | Unit tests for document editing, undo/redo, naming, relationships, drag/drop logic. |
| scripts/guibuilder/common/src/main/java/com/codename1/guibuilder/ui/GuidedLayoutSupport.java | Applies name-based Guided Layout constraints into LayeredLayout at preview/runtime. |
| scripts/guibuilder/common/src/main/java/com/codename1/guibuilder/ui/DragGuideOverlay.java | Overlay painting for drag/drop guides, selection, and simulated layout previews. |
| scripts/guibuilder/common/src/main/java/com/codename1/guibuilder/ui/ComponentPreviewFactory.java | Renders live preview components from .gui XML with designer interaction hooks. |
| scripts/guibuilder/common/src/main/java/com/codename1/guibuilder/project/ProjectIO.java | Loads binding + reads/writes GUI/CSS/source content via FileSystemStorage. |
| scripts/guibuilder/common/src/main/java/com/codename1/guibuilder/project/ProjectBinding.java | Binding model for guibuilder.input key/value format. |
| scripts/guibuilder/common/src/main/java/com/codename1/guibuilder/model/GuiDocument.java | Core .gui XML document model with transactions, undo/redo, and relationship management. |
| scripts/guibuilder/common/src/main/java/com/codename1/guibuilder/GuiBuilderMcpController.java | MCP tool registration and event/state streaming for automation/interaction tests. |
| scripts/guibuilder/common/src/main/css/theme.css | Editor UI theme (builder chrome styling + dark mode). |
| scripts/guibuilder/common/pom.xml | Common module config incl. cn1 plugin integration and test artifact attachment. |
| scripts/guibuilder/common/codenameone_settings.properties | GUI Builder CN1 settings (Java 17, desktop defaults, theme flags). |
| scripts/guibuilder/.gitignore | Ignores build output + generated binding input file for demo project. |
| maven/update-version.sh | Extends version bump script to include the new GUI Builder reactor. |
| maven/core-unittests/src/test/java/com/codename1/ui/layouts/LayeredLayoutTest.java | Adds regression test for true-baseline alignment with padding/margins. |
| maven/core-unittests/src/test/java/com/codename1/ui/CodeEditorTest.java | Adds regression tests for protected markers and caret movement. |
| maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/OpenGuiBuilderMojoTest.java | Tests binding output, property forwarding, desktop identity args, and project dir detection. |
| maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/OpenGuiBuilderMojo.java | Replaces legacy jar-based GUI Builder launch with Maven-resolved Java 17 editor launch. |
| CodenameOne/src/com/codename1/ui/Tabs.java | Adds null-safety around getComponentForm() during gesture handling. |
| CodenameOne/src/com/codename1/ui/layouts/LayeredLayout.java | Updates baseline unit behavior to use declared baselines only when resize behavior is declared. |
| CodenameOne/src/com/codename1/ui/editor/PureEditor.java | Adds a setCursor command for pure editor caret positioning. |
| CodenameOne/src/com/codename1/ui/editor/CodeView.java | Adds protected region markers that block edits to generated ranges. |
| CodenameOne/src/com/codename1/ui/editor/CodePureEditor.java | Wires setProtectedMarkers command into CodeView protected-region support. |
| CodenameOne/src/com/codename1/ui/CodeEditor.java | Public API for protected region markers and caret positioning. |
| CodenameOne/src/com/codename1/components/SplitPane.java | Adds null-safety around getComponentForm() during init. |
| .github/workflows/release-on-maven-central.yml | Extends release workflow to deploy/confirm/publish GUI Builder alongside other editors. |
| .github/workflows/guibuilder.yml | Adds dedicated CI workflow to build core + compile/test/package the standalone GUI Builder. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6b3fb02eb7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 151 screenshots: 151 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
Cloudflare Preview
|
|
Compared 181 screenshots: 181 matched. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 45 out of 46 changed files in this pull request and generated no new comments.
Suppressed comments (4)
CodenameOne/src/com/codename1/ui/editor/CodeView.java:163
- Edits at the exact end of a protected block (i.e., immediately after the end marker) are currently treated as "inside" the protected region because the caret check uses
<= protectedEnd. This prevents inserting text right after the generated block, which should be allowed.
if ((start == end && start >= protectedStart && start <= protectedEnd)
|| (start < protectedEnd && end > protectedStart)) {
return true;
scripts/guibuilder/tools/guibuilder-mcp-client.mjs:23
- Incoming messages are parsed with
JSON.parse(line)without error handling. If the server emits a malformed line (or the stream is corrupted), the client will crash rather than reporting a parse error and continuing.
scripts/guibuilder/common/src/main/java/com/codename1/guibuilder/project/ProjectIO.java:87 fsUrl()currently just prependsfile://without normalizing Windows paths. On Windows this will produce invalid file URLs (e.g.file://C:\Users\...), and consumers that stripfile://can end up with drive-relative paths (the same pitfall covered by scripts/settings ProjectIOTest). This can break project file reads/writes on Windows.
public static String fsUrl(String path) {
if (path == null || path.startsWith("file://") || path.indexOf("://") > 0) return path;
return "file://" + path;
}
scripts/guibuilder/tools/guibuilder-mcp-client.mjs:12
portis parsed withNumber(...)and used directly innet.createConnection(). If the env var/arg is non-numeric, this becomesNaNand the client fails with a low-signal runtime error. Validating the port early provides a clearer failure mode.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f0099dabf7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a745163cb4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 46 out of 47 changed files in this pull request and generated no new comments.
Suppressed comments (4)
CodenameOne/src/com/codename1/ui/editor/CodeView.java:163
- The protected-region check treats the end marker as inclusive for insertions (
start <= protectedEnd), which blocks edits immediately after the closing marker (i.e., at the first character following the protected block). This makes it hard to place user code right after a generated section.
if ((start == end && start >= protectedStart && start <= protectedEnd)
|| (start < protectedEnd && end > protectedStart)) {
return true;
scripts/guibuilder/tools/guibuilder-mcp-client.mjs:26
JSON.parse(line)in the socket data handler is unguarded. Any malformed/partial line from the server will throw and crash the client process, leaving pending requests unresolved.
scripts/guibuilder/tools/guibuilder-mcp-client.mjs:43request()resolves even when the JSON-RPC response contains anerrorobject, so callers proceed as if initialization/tool calls succeeded. This should reject the promise on JSON-RPC errors.
maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/OpenGuiBuilderMojo.java:68- The binding file name includes a random UUID, so every
mvn cn1:guibuilderrun leaves another staleguibuilder-*.inputin~/.codenameoneGUIBuilder. Over time this can accumulate unnecessarily.
File runtimeDir = new File(System.getProperty("user.home"), ".codenameoneGUIBuilder");
runtimeDir.mkdirs();
File input = new File(runtimeDir, "guibuilder-" + UUID.randomUUID() + ".input");
writeBinding(input, projectDir, guiDir, sourceDir, cssFile);
|
Compared 148 screenshots: 148 matched. Benchmark Results
Detailed Performance Metrics
|
|
Compared 143 screenshots: 143 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 52 out of 53 changed files in this pull request and generated no new comments.
Suppressed comments (4)
CodenameOne/src/com/codename1/ui/editor/CodeView.java:163
- Protected-region edit detection treats an insertion exactly at the end marker boundary as protected ("<= protectedEnd"), which prevents typing immediately after a generated block. This makes it impossible to add code right after the protected end marker without first moving further away.
if ((start == end && start >= protectedStart && start <= protectedEnd)
|| (start < protectedEnd && end > protectedStart)) {
return true;
CodenameOne/src/com/codename1/ui/layouts/LayeredLayout.java:3688
- UNIT_BASELINE absolute pixel calculation mixes the reference component's current size with the target component's preferred size when calling getBaseline(). This can yield incorrect baseline offsets for components that are resized by constraints. Use the component's current size when available, with a preferred-size fallback.
int componentBaseline = declaredBaseline(cmp, cmp.getPreferredW(), cmp.getPreferredH());
scripts/guibuilder/tools/guibuilder-mcp-client.mjs:22
- The TCP client assumes every non-empty line is valid JSON and calls JSON.parse() without a try/catch. Any non-JSON line (e.g., server logging, partial/corrupt output) will throw and crash the client, leaving pending requests unresolved. Handle parse errors explicitly and continue reading.
CodenameOne/src/com/codename1/ui/layouts/LayeredLayout.java:3142 - UNIT_BASELINE alignment uses the reference component's current size (ref.getWidth()/getHeight()) but computes the moved component's baseline using its preferred size. If the component is laid out at a non-preferred size, this can misalign baselines. Use the component's current size when available (with a preferred-size fallback if size is still 0 at this point).
This issue also appears on line 3688 of the same file.
int componentBaseline = declaredBaseline(cmp, cmp.getPreferredW(), cmp.getPreferredH());
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 83742fbb96
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 52 out of 53 changed files in this pull request and generated no new comments.
Suppressed comments (1)
scripts/guibuilder/common/src/main/java/com/codename1/guibuilder/project/ProjectIO.java:88
fsUrl()doesn’t normalize Windows paths (backslashes) or handle drive-letter file URLs. This differs from the existing Settings/Game BuilderProjectIO.fsUrl()implementations and will break binding/file reads and directory creation on Windows (e.g.file://C:\...andensureParent()relying on/).
public static String fsUrl(String path) {
if (path == null || path.startsWith("file://") || path.indexOf("://") > 0) return path;
return "file://" + path;
}
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a48a0129f7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 217 screenshots: 217 matched. |
|
Compared 144 screenshots: 144 matched. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ab36cce029
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The guibuilder job hit the one hour limit because of two tests I added. Both focused a real text component, and focusing one starts native editing on the simulator, which blocks the EDT that callSeriallyAndWait needs -- so they hung rather than failed, which is why I never got a result out of them locally either. They are replaced by the branch that can be driven here: with nothing editable focused the key belongs to the designer, and with no editor open the Save shortcut belongs to the form. The focused-surface half is not covered, and the tests say so rather than pretending otherwise. LiveCssTest is 8 tests in 132 seconds again. Class annotations are reapplied on every save, not only during migration -- same shape as the imports and the trailing declarations before them, and the third thing that ordinary branch has had to be taught to carry. The accessors Object already declares are reserved, so a bindable component named "Class" no longer generates a getClass() that cannot override the final one and will not compile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d12bcaff86
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…guments "extends com.acme.Form" passed the check because only the simple name was compared, and the rebuilt declaration then resolved Form out of com.codename1.ui.* -- the developer's base class, and everything the carried members inherit from it, gone without a word. A qualified name is compared whole, and com.codename1.ui.Form is accepted as the real one. initGuiBuilderComponents(registerAndGetResources()) was accepted as generated work, so removing the constructor dropped a call the developer made for its side effects. Only the argument forms the scaffold wrote are accepted: nothing, Resources.getGlobalResources(), or the constructor's own parameter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9cc75b9336
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The ordinary save branch rebuilt the declaration from the template, so a class migrated with "implements Observer" quietly stopped implementing it on the next save, taking any @OverRide among the carried members with it. That is the fourth thing this branch has needed teaching to reapply, after the imports, the trailing declarations and the annotations. I named the pattern last time and still did not act on it, so this time I went through what migration does rather than waiting: everything else it adds lands inside the user region, which is carried verbatim, so this is the last of them. constructorAt() matched on the name and a parenthesis alone, so a legal "void LoginForm(Resources r)" was taken for a constructor and deleted -- its callers broken by a migration they had nothing to do with. A return type in front of the name now rules it out, and the match is a whole word. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a5f7c56ecd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A cleared Event field on a toolbar command emitted "this::null", which does not compile. An unhandled command is a reasonable thing to want, so it is added with an empty listener rather than refused. An Advanced inset the layout cannot parse was swallowed: the canvas stayed on the previous constraint while the document kept the text, and the same text reached LayeredLayout.setInsets() in the generated form, which threw while the form was being constructed. The value is taken back out of the document and the status bar says why. A Form's content pane arrives scrollable on Y and the generator emits no setter when the attribute is absent, so a canvas that forced it off disagreed with the running form. The gutter stays hidden when the scrolling is that default rather than something the document asked for -- showing it cost a few pixels of width the running form does not lose, which moved every centred component, and a centring test caught it before this went out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8414ff2369
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The inspector checkbox still carried the old false, so a Form whose content pane scrolls showed scrolling as off, and saving without touching the box looked like it would disable it and did not. Both the preview and the inspector now ask GuiDocument.defaultScrollableY(), so there is one answer rather than two that agreed until one of them changed -- which is what happened here, and the test asserts the two are equal rather than each being right on its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6d9165c637
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
"extends Form" says nothing on its own, and carriedImports() keeps whatever resolution the legacy file had, so an "import com.acme.Form" survived while the regenerated constructor called super(title, layout) on a base class that had only ever supported the implicit super(). The name is resolved the way javac would from this file: an explicit import decides it, failing that a com.codename1.ui.* import does, and a second wildcard makes it genuinely ambiguous. No import at all means a class in the same package, which is also not ours. I looked at this case when the qualified one was reported and talked myself out of it -- refusing every unqualified extends would have turned away ordinary companions, which was true, but resolving the name was the option I did not consider. One test covers all five outcomes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d1db35dd95
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
declaresActionHandler() scanned the whole file, so a trailing helper class declaring onSubmit(ActionEvent) satisfied the form's own check: the stub was left out while buildUI() still emitted this::onSubmit. It is scoped to the form's class body at member level, which also rules out an inner class or a local declaration inside a method. hasReturnType() read the annotation in "@Inject LoginForm(Resources r)" as a return type, since that declaration has no access modifier. Neither constructor path then recognised it, and the old constructor was carried into the generated class still calling the initializer that is gone. Annotations are blanked before the token in front of the name is judged -- the same helper the other declaration scanners use. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 86ab0b1a98
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The Code pane recorded its buffer as the on-disk baseline, but editing outside the markers is allowed and companionSourceFor() regenerates those regions on the way out -- so the pane reported a clean save while showing text that was never written, and closed without a word about it. The baseline is the text that reached the file, and the pane is reloaded onto it, which is also the only honest thing to show. Model regeneration was armed only by a strategy change, so adding, renaming or deleting a bindable control after the model existed left it naming the old set. UiBinding binds by exact name, so those controls stopped binding with nothing failing to say so. Save now notices when the model does not mention a bindable component and rewrites it. Only the set of names is compared: the model is generated from them, so a difference there is this generator's business, while anything else in that file is the developer's. isBindable() is extracted rather than repeated -- the type list existed once inline and this needed the same answer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4f8d9e9e86
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…rameter
The divergence check searched the model for any occurrence of the name, so
"value" matched the parameter of every generated setter and a model missing
that control looked current. Both strategies write the bound name as a string
literal -- new Property<>("email", ...) and @Bind(name = "email", ...) -- so the
literals are what is compared.
initGuiBuilderComponents(res) was accepted from a hard-coded list of likely
parameter names, so a no-argument constructor passing a field called res read
as scaffolding and the developer's resource selection went with it. An
identifier is accepted only when it is that constructor's own parameter.
Which made an earlier test wrong: it asserted "res" migrates from a no-argument
constructor, and that is exactly the case being reported. Its expectation is
corrected rather than the rule loosened to keep it green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 314c409997
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Regenerating a diverged model on save replaced a file saveModelSource() keeps exactly as typed -- fields, validation annotations and methods this generator knows nothing about, thrown away to add one property, without a word. It asks first, remembers a refusal for that form so every later save does not ask again, and MCP refuses rather than putting a modal in front of a client that cannot answer it. The divergence check counted every string literal, so a validation message equal to a component's name made an unbound control look bound -- the same false positive as the setter parameter, one step further out. Only the two forms this generator writes are read: the Property constructor's name and the @Bind name attribute. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc070d1197
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
javaEscape() deleted \r instead of escaping it. The document keeps one and the preview shows it, so saving quietly joined the surrounding text in the running application and nowhere else. Tab had the same exposure without being reported and is escaped with it. build-and-run.sh called /usr/libexec/java_home unconditionally, which exists only on macOS, and under set -e the failed substitution ended the script before any build began -- on Linux the helper appeared to do nothing at all. It uses JAVA8_HOME/JAVA21_HOME when set, then java_home where it exists, then the usual /usr/lib/jvm layout, and otherwise says which variable to set. All three paths exercised by hand, including the one where nothing is found. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5b3358b862
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…guard An MCP redo that reapplied a binding-strategy change reached syncBindingModel() and opened a modal. MCP runs synchronously on the EDT, so the client waits for a human who is not there and the call never returns. Guarding sites one at a time is what let this through -- save and refresh were each fixed when reported, and redo was not among them. Every MCP entry point now runs inside unattended(), and the confirmations ask cannotAskTheUser() before opening anything, taking the branch that keeps the user's work: the strategy prompt keeps the existing model, and the divergence prompt declines. Neither loses anything, and the next interactive save asks properly. unattended() restores the flag in a finally and reports a runtime failure as text, since an exception escaping it would leave every later confirmation suppressed. The test covers both. Callable is outside the Codename One bytecode subset -- the compliance gate caught it -- so the command is a small interface of our own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7d53216e45
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The delegate dropped the throws clause the original declared, so a caller with a dedicated catch stopped compiling -- the same broken-callers problem the delegate exists to prevent. It carries the clause, which is legal on a body that only chains. A type in the same package beats an on-demand import, so "package com.example; import com.codename1.ui.*;" makes Form mean com.example.Form when that file exists. The project has that file to look at, so it is looked at rather than the case being refused: a wildcard with no such sibling still migrates. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ab1a64a06b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The code pane checked only a pending strategy rewrite or a missing model, so adding a bindable control and saving from Code wrote the .gui and the companion against a model that does not name it -- the control stayed unbound until some later toolbar save happened to notice. The two save paths needing the same thing done twice is the shape behind a string of these reports; both now run the divergence check and the same confirmation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ec6067babc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…mbers -Xdock and the eawt exports go on only for Darwin. A Linux JVM refuses to start on the first, so every default build-and-run.sh reached its failure check twelve seconds later with nothing but a log -- the same split OpenGuiBuilderMojo.desktopIdentityArgs() already makes. "public <T> LoginForm(Resources r)" is a legal constructor whose type parameter was read as a return type, so neither constructor path recognised it and the old constructor was carried into the generated class still calling the initializer that is gone. A bare <T> is skipped while List<String> still marks a method, which the test pins in both directions. Columns, Maximum length, Rows and the slider values went through the unrestricted field, so "garbage" was stored: the preview kept its previous value and appendIntSetter() emitted nothing, leaving the canvas at 20 while the saved form used the runtime default and Save reported success. They use the validated numeric field the table sizes already used. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Settings-era GUI Builder is a Swing tool tied to the pre-Maven project
layout. This replaces it with a standalone Codename One desktop app under
scripts/guibuilder, launched bymvn cn1:guibuilder— the same shape ascn1:settingsand the Game Builder: its own Maven build, its own executableJAR, and its own Maven Central coordinates
(
com.codenameone:codenameone-guibuilder).What it does
The editor edits
.guiXML undersrc/main/guibuilderand round-trips thegenerated Java next to it, so the design surface and the source stay in step.
Generated regions of that Java are protected in the embedded code editor rather
than merely regenerated over.
Guided Layout builds on
LayeredLayoutwith builder-owned relationships —match width/height, reference targets, anchors — stored by component name.
That is why the model enforces unique names and cascades renames, deletes, and
pastes across every relationship pointing at them; a stale name is a broken
layout and a duplicate name is a duplicate Java field.
Placement adapters cover Border, Layered, Box, Flow, Grid, and Table layouts.
The whole surface is also drivable over MCP (
-Dguibuilder.mcp.port=…), whichis how the interaction tests replay complete gestures.
Core changes
Kept to the minimum the editor needs:
CodeEditorgains protected-region markers and caret positioning.LayeredLayoutUNIT_BASELINEnow uses a component's reported baselineonly when the component also describes its baseline resize behavior. The
default
Component#getBaselinereturns the bottom content edge rather than atext baseline, so without this the documented font-ascent fallback was
unreachable and containers and text areas aligned on the wrong line.
SplitPaneandTabsno longer assumegetComponentForm()isnon-null. Both can be deinitialized by the same gesture that triggers the
callback, which the builder hits routinely when it rebuilds the inspector.
Tooling
cn1:guibuildernow forwards everyguibuilder.*property, passes the desktopidentity and
--add-exportsarguments the JavaSE runtime needs, and fails witha clear message when Maven runs on a JDK older than 17 (previously an
UnsupportedClassVersionErrorburied inguibuilder.log).scripts/**is excluded from PR CI, so.github/workflows/guibuilder.ymlisadded as the only job that compiles the editor against a freshly built core —
the exact way it can otherwise rot silently. The release workflow gains the
matching Central + R2 publish/confirm steps, wired into the completion gate.
Tests
scripts/guibuilder(JDK 21)core-unittestsCodeEditorTest,LayeredLayoutTest(JDK 8)OpenGuiBuilderMojoTestscripts/guibuilder/STATUS.mdcarries the full design notes, the knownlimitations, and the phased road map.
🤖 Generated with Claude Code